package me.qi.kancha.security.configuration;

import me.qi.kancha.security.grant.custom.CustomAuthService;
import me.qi.kancha.security.grant.custom.CustomAuthServiceTag;
import me.qi.kancha.security.grant.custom.CustomTokenGranter;
import me.qi.kancha.security.authorities.AuthoritiesEnhanceTokenService;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.security.oauth2.authserver.AuthorizationServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 认证服务配置器
 *
 * @author sun
 * @date 2019/10/8 13:59
 */
@Configuration
@Import({AuthBeanConfiguration.class})//导入配置使用里面的bean
public class AuthConfiguration extends OAuth2AuthorizationServerConfiguration {

    private AuthenticationManager authenticationManager;

    private final UserDetailsService userDetailsService;

    private final PasswordEncoder passwordEncoder;

    private final DataSource dataSource;

    private final ApplicationContext applicationContext;

    private final WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator;

    private final AuthoritiesEnhanceTokenService AuthoritiesEnhanceTokenService;

    public AuthConfiguration(AuthenticationConfiguration authenticationConfiguration,
                             ObjectProvider<TokenStore> tokenStore,
                             ObjectProvider<AccessTokenConverter> tokenConverter,
                             AuthorizationServerProperties properties, UserDetailsService userDetailsService,
                             PasswordEncoder passwordEncoder, DataSource dataSource,
                             ApplicationContext applicationContext,
                             WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator,
                             AuthoritiesEnhanceTokenService AuthoritiesEnhanceTokenService) throws Exception {
        super(new BaseClientDetails(), authenticationConfiguration, tokenStore, tokenConverter, properties);
        this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
        this.dataSource = dataSource;
        this.applicationContext = applicationContext;
        this.exceptionTranslator = exceptionTranslator;
        this.AuthoritiesEnhanceTokenService = AuthoritiesEnhanceTokenService;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource).passwordEncoder(passwordEncoder);
    }



    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
        security.passwordEncoder(passwordEncoder);
        security.allowFormAuthenticationForClients();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);
        //认证服务同时也是资源服务时，设置同一个tokenService
        endpoints.tokenServices(AuthoritiesEnhanceTokenService);

        //要使用 refresh_token的话，需要额外配置  userDetailsService
        endpoints.userDetailsService(userDetailsService);

        //异常处理
        endpoints.exceptionTranslator(exceptionTranslator);

        //开启密码授权类型 endpoints.getTokenGranter()中构建 ResourceOwnerPasswordTokenGranter 需要该参数
        /*endpoints.authenticationManager(authenticationManager);*/

        //granter构建需要的参数
        ClientDetailsService clientDetails = endpoints.getClientDetailsService();
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();

        List<TokenGranter> granters = new ArrayList<>();
        //默认granter （不包含 client_credentials）
        TokenGranter defaultGranter = getDefaultGranter(tokenServices, clientDetails, requestFactory, authorizationCodeServices);
        granters.add(defaultGranter);

        //client_credentials 模式重写
        AbstractTokenGranter clientCredentialsTokenGranter =
                new AbstractTokenGranter(tokenServices, clientDetails, requestFactory, "client_credentials"){};
        granters.add(clientCredentialsTokenGranter);

        //自动加载自定义模式
        Map<String, Object> beans = applicationContext.getBeansWithAnnotation(CustomAuthServiceTag.class);
        for (String key : beans.keySet()) {
            CustomAuthService service = (CustomAuthService) beans.get(key);
            CustomTokenGranter granter = new CustomTokenGranter(tokenServices, clientDetails, requestFactory, service);
            granters.add(granter);
        }
        endpoints.tokenGranter(new CompositeTokenGranter(granters));
    }

    /**
     * 默认granter （不包含 client_credentials）
     *
     * @param clientDetails
     * @param tokenServices
     * @param requestFactory
     * @param authorizationCodeServices
     * @return
     */
    private TokenGranter getDefaultGranter(AuthorizationServerTokenServices tokenServices,
                                                       ClientDetailsService clientDetails,
                                                       OAuth2RequestFactory requestFactory,
                                                       AuthorizationCodeServices authorizationCodeServices) {
        TokenGranter tokenGranter = new TokenGranter() {
            private CompositeTokenGranter delegate;

            @Override
            public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                if (delegate == null) {
                    List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
                    tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
                            requestFactory));
                    tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
                    ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
                    tokenGranters.add(implicit);
                    if (authenticationManager != null) {
                        tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
                                clientDetails, requestFactory));
                    }
                    delegate = new CompositeTokenGranter(tokenGranters);
                }
                return delegate.grant(grantType, tokenRequest);
            }
        };
        return tokenGranter;
    }
}
